from django.db import models
import datetime

from django.forms import ModelForm, Textarea, ModelChoiceField
from django.contrib.auth.models import User
from django.core import validators
from django.forms import forms, ModelForm, Textarea, PasswordInput, TextInput
from django import forms
from django.db.models import Q
from django.db.models import F
import random
import string
import math
import re

def letterToNum(letter):
    num = 0
    if letter == "A":
        num = 6
    elif letter == "B":
        num = 5
    elif letter == "C":
        num = 4
    elif letter == "D":
        num = 3
    elif letter == "E":
        num = 2
    elif letter == "U":
        num = 1
    return num

def compareRating(a, b):
    intA = letterToNum(a.rating)
    intB = letterToNum(b.rating)
    
    if intA > intB:
        return 1
    elif intA < intB:
        return -1
    elif intA == intB:
        if a.year > b.year:
            return 1
        elif a.year < b.year:
            return -1
        elif a.year == b.year:
            return 0

class MetaInfo(models.Model):
    createdBy = models.ForeignKey(User, related_name='_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return str(self.editedBy) + ":" + str(self.editedOn)
    
class RefRating(models.Model):
    REFEREE_RATING_CHOICES = (
        ('1', '1'),
        ('2', '2'),
        ('3', '3'),
        ('4', '4'),
        ('5', '5'),
        ('6', '6'),
        ('7', '7'),
        ('8', '8'),
        ('9', '9'),
        ('10', '10'),
        ('P', 'P')
    )
    
    thisYear = datetime.datetime.now().year
    YEAR_CHOICES = (
        (str(thisYear), str(thisYear)),
        (str(thisYear-1), str(thisYear-1)),
        (str(thisYear-2), str(thisYear-2)),
        (str(thisYear-3), str(thisYear-3)),
        (str(thisYear-4), str(thisYear-4)),
    )
    
    rating = models.CharField(max_length=2, choices=REFEREE_RATING_CHOICES)
    year = models.CharField(max_length=4, choices=YEAR_CHOICES, blank=True)
    
    def __str__(self):
        return self.rating+"("+self.year+")"
    
    # Ensures each rating and year combination is unique
    class Meta:
        unique_together = ('rating', 'year')

class WeaponRating(models.Model):
    WEAPON_RATING_CHOICES = (
        ('A', 'A'),
        ('B', 'B'),
        ('C', 'C'),
        ('D', 'D'),
        ('E', 'E'),
        ('U', 'U')
    )
    
    thisYear = int(datetime.datetime.now().year)
    YEAR_CHOICES = (
        (str(thisYear), str(thisYear)),
        (str(thisYear-1), str(thisYear-1)),
        (str(thisYear-2), str(thisYear-2)),
        (str(thisYear-3), str(thisYear-3)),
        (str(thisYear-4), str(thisYear-4)),
    )
    
    rating = models.CharField(max_length=1, choices=WEAPON_RATING_CHOICES)
    year = models.CharField(max_length=4, choices=YEAR_CHOICES, blank=True, null=True)
    
    def __str__(self):
        if str(self.rating) != "U":
            ret = str(self.rating)+str(self.year)
        else:
            ret = str(self.rating)
        return ret
    
    # Ensures that each rating and year pair is unique
    class Meta:
        unique_together = ('rating', 'year')
        ordering = ['rating', '-year']

class Section(models.Model):
    name = models.CharField(primary_key=True, max_length=100)
    
    def __str__(self):
        return self.name

class Division(models.Model):
    name = models.CharField(primary_key=True, max_length=50)
    #section = models.ForeignKey(Section)
    
    def __str__(self):
        return self.name

class Club(models.Model):
    name = models.CharField(unique=True, max_length=50)
    abbreviation = models.CharField(max_length=8)
    division = models.ForeignKey(Division)
    owners = models.ManyToManyField(User)
    
    city = models.CharField(max_length=50)
    state = models.CharField(max_length=25)
    street = models.CharField(max_length=100)
    
    webSite = models.URLField("Web Site", blank=True, null=True)
    
    createdBy = models.ForeignKey(User, related_name='club_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='club_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return self.name;
    
    def mapifyAddress(self):
        fixMe = self.street+","+self.city+","+self.state
        return string.replace(fixMe," ","+")
    
class Person(models.Model):
    firstName = models.CharField(max_length=30)
    lastName = models.CharField(max_length=30)
    gender = models.CharField(max_length=6, choices=(('F', 'Female'),('M', 'Male')))
    birthday = models.DateField('Birthday', blank=True, null=True)
    #usfaNumber = models.ForeignKey(UsfaCard, related_name="UsfaNumber", verbose_name="USFA #", blank=True, null=True)
    email = models.EmailField(verbose_name="Email", unique=True, blank=True, null=True)
    
    primaryClub = models.ForeignKey(Club, related_name="PrimaryClub", verbose_name="Primary")
    secondaryClub = models.ForeignKey(Club, related_name="SecondaryClub", verbose_name="Secondary", blank=True, null=True)
    
    account = models.OneToOneField(User, blank=True, null=True)

    createdBy = models.ForeignKey(User, related_name='person_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='person_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def printedName(self):
        return self.lastName+", "+self.firstName
    
    def __str__(self):
        return self.printedName()
        
    def normalName(self):
        return self.firstName + " " + self.lastName
    
    def getFencingRating(self, weapon):
        try:
            card = UsfaCard.objects.all().get(person=self.id)
        except Exception as e:
            pass
        
        if weapon == "Foil":
            return card.fencingFoilRating
        elif weapon == "Epee":
            return card.fencingEpeeRating
        elif weapon == "Saber":
            return card.fencingSaberRating

    def getFoilRating(self):
        try:
            card = UsfaCard.objects.all().get(person=self.id)
            return card.fencingFoilRating
        except Exception as e:
            print e
            return "U"
        
    def getEpeeRating(self):
        try:
            card = UsfaCard.objects.all().get(person=self.id)
            return card.fencingEpeeRating
        except Exception as e:
            print e
            return "U"
        
    def getSaberRating(self):
        try:
            card = UsfaCard.objects.all().get(person=self.id)
            return card.fencingSaberRating
        except Exception as e:
            print e
            return "U"
        
    class Meta:
        ordering = ['lastName', 'firstName']
        
class UsfaCard(models.Model):
    number = models.IntegerField("USFA #", unique = True, max_length=9, blank=True, null=True)
    division = models.ForeignKey(Division)
    verified = models.BooleanField()
    
    issueDate = models.DateField("Issued", blank=True, null=True,)
    expirationDate = models.DateField("Expires", blank=True, null=True,)
    
    fencingFoilRating = models.ForeignKey(WeaponRating, related_name="FencingFoilRating", verbose_name="Foil")
    fencingEpeeRating = models.ForeignKey(WeaponRating, related_name="FencingEpeeRating", verbose_name="Epee")
    fencingSaberRating = models.ForeignKey(WeaponRating, related_name="FencingSaberRating", verbose_name="Saber")
    
    refFoilRating = models.ForeignKey(RefRating, related_name="RefFoilRating", blank=True, null=True, verbose_name="Foil Reffing")
    refEpeeRating = models.ForeignKey(RefRating, related_name="RefEpeeRating", blank=True, null=True, verbose_name="Epee Reffing")
    refSaberRating = models.ForeignKey(RefRating, related_name="RefSaberRating", blank=True, null=True, verbose_name="Saber Reffing")
    
    person = models.ForeignKey(Person)
    
    createdBy = models.ForeignKey(User, related_name='usfaCard_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='usfaCard_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return str(self.number)
    
class Tournament(models.Model):
    name = models.CharField(max_length=200)
    hostClub = models.ForeignKey(Club, verbose_name="Host", blank=True, null=True)
    
    city = models.CharField(max_length=50)
    state = models.CharField(max_length=25)
    street = models.CharField(max_length=100)

    official = models.BooleanField("USFA Official")
    details = models.CharField(max_length=2000, blank=True, null=True)
    entryFee = models.FloatField("Entry Fee", blank=True, null=True)
    eventFee = models.FloatField("Event Fee", blank=True, null=True)
    
    owners = models.ManyToManyField(User)
    
    createdBy = models.ForeignKey(User, related_name='tournament_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='tournament_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return self.name

class Event(models.Model):
    GENDER_CHOICES = (
        ("Mixed", "Mixed"),
        ("Women", "Women"),
        ("Men", "Men")
    )

    AGE_CHOICES = (
        ("open", "Open"),
        ("vet70", "Veteran 70"),
        ("vet60", "Veteran 60"),
        ("vet50", "Veteran 50"),
        ("vet40", "Veteran 40"),
        ("vetcombined", "Veteran"),
        ("senior", "Senior"),
        ("junior", "Junior"),
        ("cadet", "Cadet"),
        ("Y19", "Youth 19"),
        ("Y14", "Youth 14"),
        ("Y12", "Youth 12"),
        ("Y10", "Youth 10"),
        ("Y8", "Youth 8")
    )
    
    WEAPON_CHOICES = (
        ("Foil", "Foil"),
        ("Epee", "Epee"),
        ("Saber", "Saber"),
    )
    
    WEAPON_RATING_CHOICES = (
        ('A', 'A'),
        ('B', 'B'),
        ('C', 'C'),
        ('D', 'D'),
        ('E', 'E'),
        ('U', 'U')
    )

    limitGender = models.CharField(max_length=6, choices=GENDER_CHOICES, verbose_name="Gender")
    limitAge = models.CharField(max_length=25, choices=AGE_CHOICES, verbose_name="Age")
    limitWeapon = models.CharField(max_length=5, choices=WEAPON_CHOICES, verbose_name="Weapon")
    limitMaxRating = models.CharField(max_length=1, choices=WEAPON_RATING_CHOICES, verbose_name="Max Allowed Rating", blank=True, null=True)
    limitMinRating = models.CharField(max_length=1, choices=WEAPON_RATING_CHOICES, verbose_name="Min Allowed Rating", blank=True, null=True)
    
    """
    0 - Taking preregistrations. Event has not began.
    1 - Not taking preregistrations. Initial seeding has been set. Waiting on clusters to be built
    2 - Clusters have been built. Waiting to begin first round of fencing.
    3 - Fencing has begun. Waiting for all clusters to finish.
    4 - Round is completed. Waiting for data to be entered and to seed new round.
    5 - New round seedings have been posted. Fencing has not yet resumed again.
    6 - The event has been completed.
    """
    status = models.IntegerField(blank=True, null=True, default=0)

    start = models.DateTimeField("Start Day/Time")
    
    tournament = models.ForeignKey(Tournament)
    
    createdBy = models.ForeignKey(User, related_name='event_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='event_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        ratingString = ""
        if self.limitMaxRating == "A" and (self.limitMinRating == "U" or self.limitMinRating == None):
            ratingString = ""
        elif self.limitMaxRating == None and self.limitMinRating == None:
            ratingString = ""
        elif self.limitMaxRating == self.limitMinRating:
            ratingString = self.limitMaxRating
        elif self.limitMaxRating == "U" and self.limitMinRating == None:
            ratingString = "U"
        elif (self.limitMaxRating != "U" and self.limitMaxRating != None) and (self.limitMinRating == "U" or self.limitMinRating == None):
            ratingString = self.limitMaxRating + " and Under"
        elif (self.limitMaxRating == None) and (self.limitMinRating != "U" and self.limitMinRating != None):
            ratingString = self.limitMinRating + " and Over"
        else:
            ratingString = self.limitMaxRating + "-" + self.limitMinRating
        return self.limitGender + " " + self.limitAge + " " + ratingString + " " + self.limitWeapon
    
    def eventName(self):
        return self.tournament.name + " - " + str(self)
    
    def getStatus(self):
        if self.status == 0:
            return "Taking preregistation"
        elif self.status == 1:
            return "Preregistration closed"
    
    def getReferees(self):
        return Referee.objects.all().filter(event=self.id)
        
    def getPossibleEntrants(self):
        if self.limitGender == "Mixed":
            possible = Person.objects.all()
        if self.limitGender == "Women":
            possible = Person.objects.all().filter(gender="F")
        if self.limitGender == "Men":
            possible = Person.objects.all().filter(gender="M")
            
        # Remove people who are already entered
        alreadyEntered = Entrant.objects.all().filter(event=self.id)
        for entrant in alreadyEntered:
            possible = possible.exclude(id=entrant.identity.id)
        
        # Need to implement rating checking
        #min = self.limitMinRating
        #max = self.limitMaxRating
        #
        #minNum = letterToNum(min)
        #maxNum = letterToNum(max)
        #
        #print "min: " + str(minNum) + " max: " + str(maxNum)
        return possible
    
    def generateInitialSeeding(self, user):

        # In the first round, we always seed by rating
        if self.getCurrentRound().roundNum == 1:
            entrants = self.getEntrants()
            if self.limitWeapon == "Foil":
                entrants = entrants.order_by('identity__usfacard__fencingFoilRating__rating', '-identity__usfacard__fencingFoilRating__year')
            elif self.limitWeapon == "Epee":
                entrants = entrants.order_by('identity__usfacard__fencingEpeeRating__rating', '-identity__usfacard__fencingEpeeRating__year')
            elif self.limitWeapon == "Saber":
                entrants = entrants.order_by('identity__usfacard__fencingSaberRating__rating', '-identity__usfacard__fencingSaberRating__year')
            stuff = []
            
            # Shuffle all people with the same ratings
            for rating in WeaponRating.objects.all():
                if self.limitWeapon == "Foil":
                    someEntrants = entrants.all().filter(identity__usfacard__fencingFoilRating=rating.id)
                elif self.limitWeapon == "Epee":
                    someEntrants = entrants.all().filter(identity__usfacard__fencingEpeeRating=rating.id)
                elif self.limitWeapon == "Saber":
                    someEntrants = entrants.all().filter(identity__usfacard__fencingSaberRating=rating.id)
                someStuff = []
                someStuff.extend(someEntrants)
                random.shuffle(someStuff)
                stuff.extend(someStuff)
                
        # Otherwise, we sort by their place, how they did in the last round
        else:
            entrants = Fencer.objects.all().filter(round=self.getLastRound()).order_by('place')
            stuff = []
            stuff.extend(entrants)
        
        thisRound = self.getCurrentRound()
        Fencer.objects.all().filter(round=thisRound).delete()
        c = 1
        for entrant in stuff:
            card = UsfaCard.objects.all().get(person=entrant.identity)
            Fencer.objects.create(identity=entrant.identity,
                                  editedBy=user,
                                  createdBy=user,
                                  number=c,
                                  place=c,
                                  round=thisRound,
                                  leftHanded=False,
                                  state=0,
                                  yellowCarded=False,
                                  redCardCount=0,
                                  primaryClub=entrant.identity.primaryClub,
                                  secondaryClub=entrant.identity.secondaryClub,
                                  foilRating=card.fencingFoilRating,
                                  epeeRating=card.fencingEpeeRating,
                                  saberRating=card.fencingSaberRating)
            c = c + 1
        return stuff
    
    def getEntrants(self):
        x =  Entrant.objects.all().filter(event=self)
        return (x)
    
    def getEntrantIdentities(self):
        personList = []
        for entrant in Entrant.objects.all().filter(event=self):
            personList.append(entrant.identity.id)
        return personList
    
    
    def getLastRound(self):
        try:
            round = Round.objects.get(event=self, roundNum=self.getCurrentRound().roundNum-1)
        except:
            return self.getCurrentRound()
        return round
    
    def getCurrentRound(self):
        rounds = Round.objects.all().filter(event=self).order_by("roundNum")
        
        for round in rounds:
            if not round.isFinished:
                return round
            
        return None
        
        # This returns the newest object by date, this could be bad. We want the last object in the list
        #return rounds.latest()
    
    def getNextRound(self):
        try:
            round = Round.objects.get(event=self, roundNum=self.getCurrentRound().roundNum+1)
        except:
            return self.getCurrentRound()
        return round
    
class Entrant(models.Model):
    identity = models.ForeignKey(Person)
    hasPaid = models.BooleanField("Paid")
    event = models.ForeignKey(Event)
    
    createdBy = models.ForeignKey(User, related_name='entrant_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='entrant_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
        
    def __str__(self):
        return self.identity.printedName()
        
    # Ensures that each person can only enter an event once
    class Meta:
        unique_together = ('identity', 'event')
        ordering = ['identity']

class Strip(models.Model):
    tournament = models.ForeignKey(Tournament)
    number = models.PositiveIntegerField("Number")
    
    grounded = models.BooleanField()
    outOfService = models.BooleanField(verbose_name="Broken")
    
    createdBy = models.ForeignKey(User, related_name='strip_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='strip_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return str(self.number)
    
    class Meta:
        unique_together = ('tournament', 'number')
    
class Referee(models.Model):
    identity = models.ForeignKey(Person)
    event = models.ForeignKey(Event)
    strip = models.ForeignKey(Strip, blank=True, null=True)
    
    createdBy = models.ForeignKey(User, related_name='referee_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='referee_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return self.identity.printedName()
        
    def normalName(self):
        return self.identity.normalName()
    
class Round(models.Model):
    ROUND_TYPE_CHOICES = (
        (1, "Pools"),
        (2, "DE's"),
    )
    event = models.ForeignKey(Event)
    
    type = models.IntegerField("Round Type", choices=ROUND_TYPE_CHOICES)
    roundNum = models.IntegerField("Round Number")
    isFinished = models.BooleanField("Is Finished", blank=True, default=False)
    
    # 1.0 = 100%
    promotionRate = models.FloatField()
    
    createdBy = models.ForeignKey(User, related_name='round_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='round_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return str(self.type)
        
    def getType(self):
        if self.type == 1:
            return "Pool"
        if self.type == 2:
            return "DE"
        
    def generatePoolOptions(self):
        numEntrants = Entrant.objects.all().filter(event=self.event)
        numEntrants = numEntrants.count()
        #numEntrants = 142
        options = []
        sizes = []
        for i in range(5, numEntrants/2 + 2):
            sizes.append(i)
            
        for size in sizes:
            i = numEntrants % size
            bigPoolNum = math.floor(numEntrants / size)
            for c in range(i, size-1):
                bigPoolNum = bigPoolNum - 1
                
            message = ""
            
            bigPoolNum = int(bigPoolNum)
            
            message = str(size-i+bigPoolNum) + " pools: "
            if int(bigPoolNum) > 1:
                message = message +  str(bigPoolNum) + " pools of " + str(size) + " and "
            elif int(bigPoolNum) == 1:
                message = message + str(bigPoolNum) + " pool of " + str(size) + " and "
            else:
                pass
            
            if size - i == 1:
                message = message + str(size-i) + " pool of " + str(size-1)
            else:
                message = message + str(size-i) + " pools of " + str(size-1)
                
            if ((size-i) * (size-1)) + (math.fabs(bigPoolNum) * size) == numEntrants:
                options.append(message)
        
        options.append("1 pool: 1 pool of " + str(numEntrants))
        options.sort(self.organize)
        return options
    
    # TODO: EVENTUALLY TAKE INTO ACCOUNT TIES AT VARIOUS PLACES
    def determineDePlacement(self):
        clusters = Cluster.objects.all().filter(round=self).order_by('number')
        fencers = Fencer.objects.all().filter(round=self).order_by('number')
        
        # All the losers together
        losers = []
        
        # For each cluster...
        for cluster in clusters:
            loserList = []
            bouts = Bout.objects.all().filter(cluster=cluster)
            
            # And for each bout, get all the losers
            for bout in bouts:
                loserList.append(bout.getLoser())
            
            # Sort the losers based on their numbers (initial seeding), descending
            loserList.sort(key=lambda x: x.number, reverse=False)
            
            losers = loserList + losers
        
        # And eventually prepend the winner to the list
        for fencer in fencers:
            if fencer not in loserList:
                losers.reverse()
                losers.append(fencer)
                losers.reverse()
                break
        
        # Return the list of everyone in placement order, because we're all losers deep down
        return losers
        
    def generateDeTable(self, user):
        fencers = Fencer.objects.all().filter(round=self).order_by('place')
        clusters = Cluster.objects.all().filter(round=self)
        
        #clusters.delete()
        #print "woooooo progress!"
        
        # If the Table is already generated, don't do it again
        try:
            if len(clusters) != 0:
                return
        except:
            pass
        numFencers = len(fencers)
        
        roundSize = numFencers
        
        # Add one until we have a power of 2 number of fencers
        while math.log(roundSize, 2) != math.floor(math.log(roundSize, 2)):
            roundSize = roundSize + 1
            
        # The total number of byes that will be handed out
        numByes = roundSize - numFencers
        
        # The total number of byes left to be assigned
        byesToGo = numByes
        
        # The total number of rounds of FENCING (a bracket of 2 people has a numRounds = 1)
        numRounds = 0
        tmp = roundSize
        
        # Set the number of rounds
        while tmp != 1:
            tmp = tmp / 2
            numRounds = numRounds + 1
        
        # Build each of the cluster
        
        newCluster = Cluster(round=self, number=1, createdBy=user, editedBy=user)
        newCluster.save()

        # Get the shiny new clusters
        clusters = Cluster.objects.all().filter(round=self)
        
        topHalf = fencers.filter(place__lte=roundSize/2 )
        
        
        tableClusters = Cluster.objects.all().filter(round=self)
        
        # The place of an opponent to someone in the top half
        opponentPlace = roundSize
        # For each of the fencers in the top half...
        for fencer in topHalf:
            
            # If a bye still has to be given out, give one
            if byesToGo > 0:
                byesToGo = byesToGo - 1
                leftFencer = fencer
                leftFencer.save()
                rightFencer = None
            
            
            # Otherwise, assign two fencers to a bout
            else:
                leftFencer = fencer
                rightFencer = fencers.get(place=opponentPlace)
                leftFencer.save()
                rightFencer.save()
                
            opponentPlace = opponentPlace - 1
            
            try:
                bout = Bout(leftFencer=leftFencer,
                rightFencer=rightFencer,
                cluster=newCluster,
                createdBy=user,
                editedBy=user,
                maxScore=15)
                bout.save()
            except Exception as e:
                print e
        
        peeps = len(fencers)
        sizeMult = 2
        numBouts = len(Bout.objects.all().filter(cluster=newCluster))
        
        # For each round (except the first)...
        for i in range(2,numRounds+1):
            lastCluster = newCluster
            numBouts = numBouts/2 + 1
            
            # Make the new round
            try:
                newCluster = Cluster(round=self, number=i, createdBy=user, editedBy=user)
                newCluster.save()
            except Exception as e:
                print e
            
            # Make the appropriate number of new bouts
            for c in range(1, numBouts):
                try:
                    lastBouts = Bout.objects.all().filter(cluster=lastCluster, nextBout=None)
                    print "last: " + str(lastBouts)
                    
                    bout = Bout(leftFencer=None,
                    rightFencer=None,
                    cluster=newCluster,
                    createdBy=user,
                    editedBy=user,
                    maxScore=15)
                    bout.save()
                    
                    # TODO: THIS LOGIC IS POTENTIALLY REALLY REALLY REALLY BAD
                    # THERE IS ABSOLUTELY NO GUARANTEE THAT THE FIRST TWO BOUTS I PICK OFF
                    # WILL BE THE CORRECT ONES!!! NEED TO FIND A WAY TO SEARCH BY NUMERS OR SOMETHING
                    lastBoutA = lastBouts[0]
                    lastBoutB = lastBouts[1]
                    
                    lastBoutA.nextBout = bout
                    lastBoutB.nextBout = bout
                    
                    lastBoutA.save()
                    lastBoutB.save()
                except Exception as e:
                    print e
            
            # Decrease the size by two
            sizeMult = sizeMult * 2
    
    def generatePools(self, totalPools, totalBig, bigSize, totalSmall, user, primary, secondary, name, division):
        fencers = Fencer.objects.all().filter(round=self)
        clusters = Cluster.objects.all().filter(round=self)
        clusters.delete()
        pools = []
        # Make the new pools
        for i in range (0, totalPools):
            Cluster.objects.all().create(
                round=self,
                number=i+1,
                createdBy=user,
                editedBy=user
            )
        
        # Get all the clusters
        clusters = Cluster.objects.all().filter(round=self)
        ascending = True
        lastCluster = 0
        
        # For each fencer, work down the ordered list of pools, placing one fencer in each pool
        for fencer in fencers:
            if ascending:
                lastCluster = lastCluster + 1
            else:
                lastCluster = lastCluster - 1
            
            # Once we hit the end of the list of pools, set the current pool to the last/first one in the list, and then traverse in the opposite direction
            try:
                cluster = Cluster.objects.all().get(round=self, number=lastCluster)
            except:
                if ascending:
                    lastCluster = lastCluster - 1
                else:
                    lastCluster = lastCluster + 1
                ascending = not ascending
                cluster = Cluster.objects.all().get(round=self, number=lastCluster)
                
            # Set the fencer's pool, and save it
            fencer.cluster = cluster
            fencer.save()
            
        
        #fencers = Fencer.objects.all().filter(round=self).order_by('?')
        #weapon = self.event.limitWeapon
        #intFencers = {}
        #maxOfClub = {}
        #minOfClub = {}
        #clubDic = {}
        #pools = []
        #clubsInPools = []
        #
        #clubList = []
        #clubList.extend(Club.objects.all().filter(PrimaryClub__fencer__round=self))
        #for club in clubList:
        #    maxOfClub[club] = math.ceil(float(clubList.count(club))/totalPools)
        #    minOfClub[club] = math.floor(float(clubList.count(club))/totalPools)
        #    clubDic[club] = 0
        #
        ## Remove all clusters that might already be in this round
        #Cluster.objects.all().filter(round=self).delete()
        #
        #for fencer in Fencer.objects.all().filter(round=self):
        #    fencer.cluster = None
        #    fencer.save()
        #
        ## Make the new pools
        #for i in range (0, totalPools):
        #    clubsInPools.append(clubDic)
        #    pools.append([])
        #    Cluster.objects.all().create(
        #        round=self,
        #        number=i+1,
        #        createdBy=user,
        #        editedBy=user
        #    )
        #
        #clusters = Cluster.objects.all().filter(round=self)
        #
        ## Turn the fencer's rating into a number, and associate them in the dictionary
        #for fencer in fencers:
        #    rating = fencer.getFencingRating(weapon)
        #    
        #    # Only sort by ratings if it's the initial round
        #    if self.roundNum == 1:
        #        if rating.year == None:
        #            intFencers[fencer] = (letterToNum(rating.rating) * 10)
        #        else:
        #            intFencers[fencer] = (letterToNum(rating.rating) * 10) + (4 - (datetime.datetime.now().year - int(rating.year)))
        #    else:
        #        intFencers[fencer] = fencer.place
        #
        #for rating in WeaponRating.objects.all():
        #    if weapon == "Foil":
        #        fencers = Fencer.objects.all().filter(foilRating=rating.id, round=self).order_by('?')
        #    elif weapon == "Epee":
        #        fencers = Fencer.objects.all().filter(epeeRating=rating.id, round=self).order_by('?')
        #    elif weapon == "Saber":
        #        fencers = Fencer.objects.all().filter(saberRating=rating.id, round=self).order_by('?')
        #    
        #    # For each fencer... (Starting with the strongest)
        #    for fencer in fencers:
        #        poolRating = 0
        #        lowestPool = -1
        #        lowestPoolRating = 99999999999
        #        
        #        # Find the number of the weakest pool...
        #        for i in range(0, len(pools)):
        #            
        #            # Skip pools that are full
        #            if i < totalBig and len(pools[i]) == bigSize:
        #                continue
        #            elif i >= totalBig and len(pools[i]) == bigSize-1:
        #                continue
        #            
        #            # If there are people in the pool...
        #            if pools[i] != []:
        #                for person in pools[i]:
        #                    poolRating = poolRating + intFencers[person]
        #                    #print "rating " + str(i) + ": " + str(poolRating)
        #                if poolRating <= lowestPoolRating:
        #                    lowestPool = i
        #                    lowestPoolRating = poolRating
        #                    
        #                poolRating = 0
        #            # If there is nobody in the pool...
        #            else:
        #                lowestPool = i
        #                lowestPoolRating = 99999999999
        #                break
        #        
        #        # And join that one
        #        pools[lowestPool].append(fencer)
        #        fencer.cluster = clusters[lowestPool]
        #        fencer.save()
        #        clubsInPools[lowestPool][fencer.primaryClub] = clubsInPools[lowestPool][fencer.primaryClub]+1
        #        
        #        # This controls swapping based on primary club
        #        if len(Fencer.objects.all().filter(cluster=fencer.cluster, identity__primaryClub=fencer.primaryClub)) > int(maxOfClub[fencer.primaryClub]):
        #            
        #            if weapon == "Foil":
        #                possibleSwaps = Fencer.objects.all().filter(round=self, foilRating=fencer.foilRating).exclude(cluster=fencer.cluster).exclude(cluster=None).exclude(identity__primaryClub=fencer.primaryClub)
        #            elif weapon == "Epee":
        #                possibleSwaps = Fencer.objects.all().filter(round=self, epeeRating=fencer.epeeRating).exclude(cluster=fencer.cluster).exclude(cluster=None).exclude(identity__primaryClub=fencer.primaryClub)
        #            elif weapon == "Saber":
        #                possibleSwaps = Fencer.objects.all().filter(round=self, saberRating=fencer.saberRating).exclude(cluster=fencer.cluster).exclude(cluster=None).exclude(identity__primaryClub=fencer.primaryClub)
        #                    
        #            
        #            if possibleSwaps:
        #                for swap in possibleSwaps:
        #                    if len(Fencer.objects.all().filter(identity__primaryClub=swap.identity.primaryClub, cluster=fencer.cluster))+1 <= int(maxOfClub[swap.identity.primaryClub]):
        #                        switch = swap
        #                        break
        #            try:
        #                tmp = fencer.cluster
        #                #switch = Fencer.objects.all().get(pk=possibleSwaps[0].id)
        #                fencer.cluster = switch.cluster
        #                switch.cluster = tmp
        #                switch.save()
        #                fencer.save()
        #                print "swapped " + str(fencer) + " and " + str(switch)
        #            except Exception as e:
        #                pass
        #return pools
    
    def organize(self, a, b):
        c = re.match('.*? .*?', a)
        u = re.match('.*? .*?', b)
        c = int(c.group(0))
        u = int(u.group(0))
        if u < c:
            return -1
        elif u > c:
            return 1
        else:
            return 0
        return 1
    
    def getBouts(self):
        return Bout.objects.all().filter(cluster__round=self)
    
    def getClusters(self):
        return Cluster.objects.all().filter(round=self)
        
    def printTable(self):
        ret = ""
        clusters = self.getClusters()
        i = 0
        
        # For each cluster...
        for cluster in clusters:
            
            # Make a table, one row long
            ret = ret + "<table class='bracket' border=0>"
            tableSize = cluster.getNumBouts() * 2
            
            # TODO: Need to add a case statement for the ending parts ("semifinals, quarterfinals, etc")
            ret = ret +  "<th>Table of " + str(tableSize) +  "</th>"
            i = i + 1
            
            bouts = cluster.getBouts()
            c = 0
            for bout in bouts:
                
                if c < len(bouts)/2:
                    top = bout.leftFencer
                    bottom = bout.rightFencer
                else:
                    top = bout.rightFencer
                    bottom = bout.leftFencer
                ret = ret + "<tr><td>"+str(top)+"</td></tr>"
                ret = ret + "<tr><td>"+str(bottom)+"</td></tr>"
                c = c + 1
            ret = ret + "</table>"
        return ret

# A group of fencers in a round. This is like a single pool for Pools, a table set in DE's, and an iteration in Round Robin
class Cluster(models.Model):
    round = models.ForeignKey(Round, verbose_name="Round")
    
    # Akin to pool number in pools, round number in De's (table of 16, 8, etc)
    number = models.PositiveIntegerField("Pool/Round #")
    
    createdBy = models.ForeignKey(User, related_name='cluster_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='cluster_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return str(self.round.getType()) + ":" + str(self.number)
    
    def isEmpty(self):
        if Fencer.objects.all().filter(cluster=self) == []:
            return True
        else:
            return False
    
    def getFencersForDe(self):
        shouldSwap = False
        print "jh" + str(Fencer.objects.all().filter(cluster=self).order_by("number"))
        fencers = Fencer.objects.all().filter(cluster=self).order_by("number")
        clone = fencers
        # TODO: Ensure this algorithm is right! Guess and check != good testing
        for i in range(0, len(clone)/2):
            b = len(clone)-(i)
            if shouldSwap:
                fencers[i], fencers[b] = fencers[b], fencers[i]
            i = i + 1
            shouldSwap = not shouldSwap
        
        return fencers
    
    def getFencers(self):
        fencers = Fencer.objects.all().filter(cluster=self).order_by("number")
        
        return fencers
        
    def getBouts(self):
        bouts = list(Bout.objects.all().filter(cluster=self))
        clone = bouts
        
        shouldSwap = False
        
        # TODO: Ensure this algorithm is right! Guess and check != good testing
        for i in range(0, len(clone)/2):
            b = len(clone)-(i)
            if shouldSwap:
                bouts[i], bouts[b] = bouts[b], bouts[i]
            i = i + 1
            shouldSwap = not shouldSwap
        return bouts
    
    def getNumBouts(self):
        return len(self.getBouts())
        
    def printDe(self):
        biggestRound = Cluster.objects.all().get(round=self.round, number=1)
        ret = "<table border=1>"
        ret = ret + "<tr>"
        ret = ret + "<td>"
        
        
        ret = ret + "</td>"
        ret = ret + "</tr>"
        ret = ret + "</table>"
        
        ret = "<html><h2>hi</h2></html>"
        return ret
    
    class Meta:
        unique_together = ('round', 'number')
        
class Fencer(models.Model):
    identity = models.ForeignKey(Person)
    number = models.PositiveIntegerField()
    leftHanded = models.BooleanField()
    place = models.IntegerField()
    round = models.ForeignKey(Round)
    cluster = models.ForeignKey(Cluster, blank=True, null=True, on_delete=models.SET_NULL)
    
    # Different numbers mean different states (fencing, injured, ondeck, etc)
    state = models.IntegerField()
    
    yellowCarded = models.BooleanField(default=False)
    redCardCount = models.IntegerField(default=0)
    
    createdBy = models.ForeignKey(User, related_name='fencer_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='fencer_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    # The ratings for each fencer at the time of the event. These should never change once the fencer is created
    # These should also be what is referenced instead of the identity's usfa card, because that changes over time.
    foilRating = models.ForeignKey(WeaponRating, related_name="foilRating", verbose_name="Foil")
    epeeRating = models.ForeignKey(WeaponRating, related_name="epeeRating", verbose_name="Epee")
    saberRating = models.ForeignKey(WeaponRating, related_name="saberRating", verbose_name="Saber")
    
    # Again, these are copied from the originial person and should never be changed.
    # This is so people can look at past tournaments and not have their information change
    primaryClub = models.ForeignKey(Club, related_name="primaryClub", verbose_name="Club I")
    secondaryClub = models.ForeignKey(Club, related_name="secondaryClub", verbose_name="Club II", blank=True, null=True)
    
    def __str__(self):
        return str(self.number) + ":" + self.identity.lastName
        
    def getFencingRating(self, weapon):
        if weapon == "Foil":
            return self.foilRating
        elif weapon == "Epee":
            return self.epeeRating
        elif weapon == "Saber":
            return self.saberRating
        
    def getVP(self):
        totalBouts = float((len(Bout.objects.all().filter(leftFencer=self,finished=True)) + len(Bout.objects.all().filter(rightFencer=self,finished=True))))
        if totalBouts < 1:
            totalBouts = 1
        return float(self.getV()) / totalBouts
    
    def getV(self):
        leftWins = len(Bout.objects.all().filter(finished=True, leftFencer=self).filter(leftScore__gte=F('rightScore')))
        rightWins = len(Bout.objects.all().filter(finished=True, rightFencer=self).filter(rightScore__gte=F('leftScore')))
        return leftWins + rightWins
    
    def getD(self):
        leftLosses = len(Bout.objects.all().filter(finished=True, leftFencer=self).filter(leftScore__lt=F('rightScore')))
        rightLosses = len(Bout.objects.all().filter(finished=True, rightFencer=self).filter(rightScore__lt=F('leftScore')))
        return leftLosses + rightLosses
    
    def getB(self):
        return self.getV() + self.getD()
    
    def getInd(self):
        return self.getTS() - self.getTR()
    
    def getTS(self):
        leftBouts = Bout.objects.all().filter(leftFencer=self)
        rightBouts = Bout.objects.all().filter(rightFencer=self)
        
        ind = 0
        for bout in leftBouts:
            ind = ind + bout.leftScore
        for bout in rightBouts:
            ind = ind + bout.rightScore
            
        return ind
    
    def getTR(self):
        leftBouts = Bout.objects.all().filter(leftFencer=self)
        rightBouts = Bout.objects.all().filter(rightFencer=self)
        
        ind = 0
        for bout in leftBouts:
            ind = ind + bout.rightScore
        for bout in rightBouts:
            ind = ind + bout.leftScore
            
        return ind
        
class Bout(models.Model):
    
    # If missing a fencer, that means the bout is a bye
    leftFencer = models.ForeignKey(Fencer, related_name="Left", blank=True, null=True)
    rightFencer = models.ForeignKey(Fencer, related_name="Right", blank=True, null=True)
    
    leftScore = models.PositiveIntegerField(default=0)
    rightScore = models.PositiveIntegerField(default=0)
    maxScore = models.PositiveIntegerField(default=5)
    
    timer = models.IntegerField(default=180)
    breakTimer = models.IntegerField(default=60)
    leftInjuryTimer = models.IntegerField(default=600)
    rightInjuryTimer = models.IntegerField(default=600)
    
    period = models.IntegerField(default=1)
    finished = models.BooleanField(default=False)
    strip = models.ForeignKey(Strip, blank=True, null=True)
    cluster = models.ForeignKey(Cluster)
    
    nextBout = models.ForeignKey('self', blank=True, null=True)
    
    createdBy = models.ForeignKey(User, related_name='bout_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='bout_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        try:
            leftPrint = str(self.leftFencer.number)
        except:
            leftPrint = "?"
            
        try:
            rightPrint = str(self.rightFencer.number)
        except:
            rightPrint = "?"
            
        return rightPrint + " - " + leftPrint
        
    def formatLeftScore(self):
        if self.rightScore != 0 and not self.finished:
            return self.leftScore
        if self.leftScore == 0 and not self.finished:
            return ""
        elif self.leftScore != 0 and not self.finished:
            return str(self.leftScore)
        elif self.finished and self.leftScore < self.rightScore:
            return "D" + str(self.leftScore)
        elif self.finished and self.leftScore > self.rightScore:
            return "V" + str(self.leftScore)
    
    def formatRightScore(self):
        if self.leftScore != 0 and not self.finished:
            return self.rightScore
        if self.rightScore == 0 and not self.finished:
            return ""
        elif self.rightScore != 0 and not self.finished:
            return str(self.rightScore)
        elif self.finished and self.rightScore < self.leftScore:
            return "D" + str(self.rightScore)
        elif self.finished and self.rightScore > self.leftScore:
            return "V" + str(self.rightScore)
            
    def isInBout(self, a):
        if len(Bout.objects.all().filter(leftFencer=a)) + len(Bout.objects.all().filter(rightFencer=a)) > 0:
            return True
        else:
            return False
        
    def prettyDePrint(self):
        ret = ""
        try:
            ret = "(" + str(fencer.number) +")" + fencer.identity.printedName()
        except:
            ret = "Bye!"
            
        return ret
    
    def getNextBout(self):
        print str(self.cluster.round) + "TACO CITY!!!"
        
    def printForDe(self):
        if self.leftFencer == None and self.rightFencer == None:
            return "? - ?"
        
        try:
            leftPrint = self.prettyDePrint(self.leftFencer)
        except:
            leftPrint = "?"
            
        try:
            rightPrint = self.prettyDePrint(self.rightFencer)
        except:
            rightPrint = "?"
            
        return leftPrint + " - " + rightPrint
    
    def getWinner(self):
        if self.finished:
            if self.leftScore > self.rightScore:
                return self.leftFencer
            if self.leftScore < self.rightScore:
                return self.rightFencer
        else:
            return None
        
    def getLoser(self):
        if self.finished:
            if self.leftScore < self.rightScore:
                return self.leftFencer
            if self.leftScore > self.rightScore:
                return self.rightFencer
        else:
            return None
    
class ListedReferee(models.Model):
    identity = models.ForeignKey(Person)
    event = models.ForeignKey(Event)
    
    createdBy = models.ForeignKey(User, related_name='listedReferee_createdBy')
    createdOn = models.DateTimeField(auto_now_add = True)
    editedBy  = models.ForeignKey(User, related_name='listedReferee_editedBy')
    editedOn  = models.DateTimeField(auto_now = True)
    
    def __str__(self):
        return self.identity.printedName()
    
class UsfaCardForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    def clean_number(self):
        data = self.cleaned_data['number']
        if len(str(data)) != 9 and data != None:
            raise forms.ValidationError("This number is not the correct length.")
        return data
    
    class Meta:
        model = UsfaCard
        exclude = {'createdBy', 'editedBy', 'issueDate', 'expirationDate', 'verified', 'person'}

class ClubForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    class Meta:
        model = Club
        exclude = {'createdBy', 'editedBy'}

class TournamentForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    class Meta:
        model = Tournament
        exclude = {'owners', 'createdBy', 'editedBy'}

class RoundForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    class Meta:
        model = Round
        exclude = {'event', 'createdBy', 'editedBy', 'roundNum'}

class RefereeForm(ModelForm):
    class Meta:
        model = Referee
        exclude = {'createdBy', 'editedBy', 'event', 'strip'}

class EntrantForm(ModelForm):
    class Meta:
        model = Entrant
        exclude = {'hasPaid', 'createdBy', 'editedBy', 'event'}

class EventForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    def clean_limitMinRating(self):
        min = self.cleaned_data['limitMinRating']
        max = self.cleaned_data['limitMaxRating']
        
        if min == None or max == None:
            return min
        
        intMin = letterToNum(min)
        intMax = letterToNum(max)
            
        if intMin > intMax:
            raise forms.ValidationError("The minimum rating must be below or equal to the maximum rating.")
        
        return min
    
    class Meta:
        model = Event
        exclude = {'createdBy', 'editedBy', 'tournament', 'status'}
        
class PersonForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    
    def clean_firstName(self):
        data = self.cleaned_data['firstName']
        data = data.capitalize()
        if not data.isalpha():
            raise forms.ValidationError("Names must contain only letters.")
        return data
    
    def clean_lastName(self):
        data = self.cleaned_data['lastName']
        data = data.capitalize()
        if not data.isalpha():
            raise forms.ValidationError("Names must contain only letters.")
        return data
    
    def clean_secondaryClub(self):
        data = self.cleaned_data['secondaryClub']
        if self.cleaned_data['primaryClub'] == data:
            raise forms.ValidationError("Primary and secondary clubs must be different.")
    
    class Meta:
        model = Person
        exclude = {'account', 'createdBy', 'editedBy'}
        
class UserForm(ModelForm):
    required_css_class = 'required'
    error_css_class = 'error'
    
    class Meta:
        model = User
        fields = ('username', 'password')
        widgets = {
            'username': TextInput(),
            'password': PasswordInput(),
        }